﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace TypeScriptJson.Generator
{
	public class GeneratorContext
	{
		public IDictionary<Type, TypeMapping> TypeMappings = new Dictionary<Type,TypeMapping>();
		
		private IContractResolver _contractResolver;
		private JsonSerializerSettings _serializerSettings;

		public IContractResolver ContractResolver
		{
			get { return _contractResolver ?? (_contractResolver = new DefaultContractResolver()); }
			set { _contractResolver = value; }
		}

		public JsonSerializerSettings SerializerSettings
		{
			get { return _serializerSettings ?? (_serializerSettings = new JsonSerializerSettings()); }
			set { _serializerSettings = value; }
		}

		public TypeMapping MapType(Type type)
		{
			Contract.Requires<ArgumentNullException>(type != null);
			Contract.Ensures(Contract.Result<TypeMapping>() != null);
			Contract.Ensures(TypeMappings[type] == Contract.Result<TypeMapping>());

			if (TypeMappings.ContainsKey(type))
				return TypeMappings[type];

			var contract = ContractResolver.ResolveContract(type);

			var mapping = contract.UnderlyingType != type
				              ? MapType(contract.UnderlyingType)
				              : contract.CreatedType != type
					                ? MapType(contract.CreatedType)
					                : CreateTypeMapping(contract);
			TypeMappings[type] = mapping;

			return mapping;
		}

		private TypeMapping CreateTypeMapping(JsonContract contract)
		{
			var primitiveContract = contract as JsonPrimitiveContract;
			if (primitiveContract != null)
				return new PrimitiveTypeMapping(primitiveContract);
			
			var objectContract = contract as JsonObjectContract;
			if (objectContract != null)
				return new ObjectTypeMapping(objectContract);

			var arrayContract = contract as JsonArrayContract;
			if (arrayContract != null)
				return new ArrayTypeMapping(arrayContract);

			var dictionaryContract = contract as JsonDictionaryContract;
			if (dictionaryContract != null)
				return new DictionaryTypeMapping(dictionaryContract);

			return new AnyTypeMapping(contract.UnderlyingType);
		}

		public void Process()
		{
			TypeMapping mapping;
			while ((mapping = TypeMappings.Values.FirstOrDefault(m => !m.IsProcessed)) != null)
			{
				mapping.Process(this);
				Contract.Assert(mapping.IsProcessed);
			}
		}
	}
}